﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Text;
using Relay.Core.Sockets;
using Relay.Core.Utility;

namespace Relay.Core.Protocol {
    /// <summary>
    /// Represents a single connection to a traditional IRC server.
    /// </summary>
    public class IrcServer {
        private readonly string[] MessageDelimiters = new string[] { "\r\n", "\n", "\r" };

        #region Fields
        private int _port = 6667;
        private string _hostName = String.Empty;
        private bool _isConnected = false;
        private string _serverName = String.Empty;
        private string _pendingMessage = String.Empty;
        private readonly IrcUserInfo _userInfo = new IrcUserInfo();
        private readonly List<IrcChannel> _channels = new List<IrcChannel>();
        private readonly TcpClientSocket _socket = new TcpClientSocket();
        #endregion

        #region Events
        /// <summary>
        /// Occurs when the connection is established to the remote server.
        /// </summary>
        public event EventHandler<EventArgs> Connected;
        /// <summary>
        /// Occurs when the connection to the remote server is terminated.
        /// </summary>
        public event EventHandler<MessageEventArgs> Disconnected;
        /// <summary>
        /// Occurs when the connection to the IRC server could not be established.
        /// </summary>
        public event EventHandler<MessageEventArgs> ConnectFailed;
        /// <summary>
        /// Occurs when a message from the server is received.
        /// </summary>
        public event EventHandler<IrcServerRawMessageReceivedEventArgs> RawMessageReceived;
        #endregion

        #region Constructor
        /// <summary>
        /// Constructs a new connection to an IRC server.
        /// </summary>
        /// <param name="hostName">The host name of the IRC server.</param>
        /// <param name="port">The port that the IRC server is listening on.</param>
        public IrcServer(string hostName, int port) {
            _hostName = hostName;
            _port = port;

            _socket.Connected += new EventHandler<EventArgs>(SocketConnected);
            _socket.ConnectFailed += new EventHandler<TcpSocketConnectFailedEventArgs>(SocketConnectFailed);
            _socket.DataReceieved += new EventHandler<TcpSocketDataReceievedEventArgs>(SocketDataReceieved);
            _socket.Disconnected += new EventHandler<TcpSocketDisconnectedEventArgs>(SocketDisconnected);
        }

        #endregion

        #region Properties
        /// <summary>
        /// Gets the host name of the remote server.
        /// </summary>
        public string HostName {
            get { return _hostName; }
            protected set { _hostName = value; }
        }

        /// <summary>
        /// Gets the port that the remote server is listening on.
        /// </summary>
        public int Port {
            get { return _port; }
            protected set { _port = value; }
        }

        /// <summary>
        /// Gets the state of the IRC connection.
        /// </summary>
        public bool IsConnected {
            get { return _isConnected; }
            protected set { _isConnected = value; }
        }

        /// <summary>
        /// Gets the friendly name of the connected server.
        /// </summary>
        public string ServerName {
            get { return _serverName; }
            protected set { _serverName = value; }
        }

        /// <summary>
        /// Gets the user info for this server connection.
        /// </summary>
        public IrcUserInfo UserInfo {
            get { return _userInfo; }
        }

        /// <summary>
        /// Gets a read only collection of all channels that this connection is on.
        /// </summary>
        public ReadOnlyCollection<IrcChannel> Channels {
            get { return new ReadOnlyCollection<IrcChannel>(_channels); }
        }
        #endregion

        #region Public Methods
        /// <summary>
        /// Begin the process of connecting to the remote server.
        /// </summary>
        public void Connect() {
            if (_isConnected) Disconnect();

            _socket.RemoteHost = _hostName;
            _socket.RemotePort = _port;

            _socket.Connect();
        }

        /// <summary>
        /// Disconnect from the remote server.
        /// </summary>
        public void Disconnect() {
            _socket.Disconnect();
        }

        /// <summary>
        /// Send a message to the remote server.
        /// </summary>
        /// <param name="text">The message to send, with a newline ('\n') appended onto the end of it.</param>
        public void SendMessage(string text) {
            if (!_isConnected)
                throw new InvalidOperationException("Not connected to a server.");

            _socket.SendText(text + "\n");
        }
        #endregion

        #region Private Methods

        #endregion

        #region Protected Methods
        /// <summary>
        /// Raises the <see cref="E:Connected"/> event.
        /// </summary>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        protected virtual void OnConnected(EventArgs e) {
            if (Connected != null) Connected(this, e);
        }

        /// <summary>
        /// Raises the <see cref="E:Disconnected"/> event.
        /// </summary>
        /// <param name="e">The <see cref="Relay.Core.MessageEventArgs"/> instance containing the event data.</param>
        protected virtual void OnDisconnected(MessageEventArgs e) {
            if (Disconnected != null) Disconnected(this, e);
        }

        /// <summary>
        /// Raises the <see cref="E:RawMessageReceieved"/> event.
        /// </summary>
        /// <param name="e">The <see cref="Relay.Core.Protocol.IrcServerRawMessageReceivedEventArgs"/> instance containing the event data.</param>
        protected virtual void OnRawMessageReceieved(IrcServerRawMessageReceivedEventArgs e) {
            if (RawMessageReceived != null) RawMessageReceived(this, e);
        }

        /// <summary>
        /// Raises the <see cref="E:ConnectFailed"/> event.
        /// </summary>
        /// <param name="e">The <see cref="Relay.Core.MessageEventArgs"/> instance containing the event data.</param>
        protected virtual void OnConnectFailed(MessageEventArgs e) {
            if (ConnectFailed != null) ConnectFailed(this, e);
        }
        #endregion

        #region Event Handlers
        void SocketDisconnected(object sender, TcpSocketDisconnectedEventArgs e) {
            OnDisconnected(new MessageEventArgs(TextUtility.GetEnumDescriptionAttribute(e.Reason)));
        }

        void SocketDataReceieved(object sender, TcpSocketDataReceievedEventArgs e) {
            string message = _pendingMessage + Encoding.ASCII.GetString(e.Data);

            // Split the message at the delimiters, then store the last one since it is either incomplete or empty,
            // and either way, it will be affixed to the next bit of data that is received.
            string[] parts = message.Split(MessageDelimiters, StringSplitOptions.None);
            for (int i = 0; i < parts.Length - 1; i++) {
                OnRawMessageReceieved(new IrcServerRawMessageReceivedEventArgs(new IrcServerMessage(parts[i])));
            }

            _pendingMessage = parts[parts.Length - 1];
        }

        void SocketConnectFailed(object sender, TcpSocketConnectFailedEventArgs e) {
            OnConnectFailed(new MessageEventArgs(e.InnerException.Message));
        }

        void SocketConnected(object sender, EventArgs e) {
            OnConnected(e);
        }
        #endregion
    }
}
